﻿// Copyright (c) 2010 Dan Popick

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Open.Web.Stijl.Configuration;

namespace Open.Web.Stijl.Css
{
    public enum ImagePosition 
    { 
        NonSpritable = -2,
        Unknown = -1,
        left, 
        center, 
        right, 
        top, 
        bottom 
    };

	public enum RepeatType
	{
		NotSpritable = -2,
		NotSpecified = -1,
		NoRepeat,
		RepeatX,
		RepeatY
	};

    /// <summary>
    /// The RuleSetFactory determines whether to create a normal RuleSet or a SpriteableRuleSet,
    /// base on the list of declarations provided to it.
    /// </summary>
    public static class RuleSetFactory
    {
		static readonly string[] AttachmentWords = new String[] { "scroll", "fixed" };
		static readonly string[] RepeatTypeValues = new String[] { "no-repeat", "repeat-x", "repeat-y" };
		static StijlConfiguration _config;

        static RuleSetFactory()
        {
            _config = StijlConfiguration.Default;
        }

        class WorkingSet
        {
            public RepeatType Repeat { get; set; }
            public string Url { get; set; }
            public ImagePosition? LeftPosition { get; set; }
            public ImagePosition? TopPosition { get; set; }
            public bool IsSpriteable { get { return _hasBackgroundUrl && !_nonCompliant; } }

            public List<Declaration> Properties { get; set; }

            bool _hasBackgroundUrl = false;
            bool _nonCompliant = false;

            public WorkingSet(List<Declaration> properties)
            {
                if (properties == null)
                {
                    Properties = new List<Declaration>(0);
                    return;
                }

                Properties = new List<Declaration>(properties.Count);

                foreach (Declaration prop in properties)
                {
					if (prop == null)
						continue;

                    switch (prop.Identifier.ToLower())
                    {
                        case "background-image":
                            ProcessImage(prop);
                            break;
                        case "background-repeat":
                            ProcessRepeat(prop);
                            break;
                        case "background-position":
                            ProcessPosition(prop);
                            break;
                        case "background":
                            if (RuleSetFactory._config.Automatic)
                                ProcessShorthand(prop);
                            else
                                Properties.Add(prop);
                            break;
                        case "sprite":
                            ProcessShorthand(prop);
                            break;
                        default:
                            Properties.Add(prop);
                            break;
                    }
                }
            }

            void ProcessImage(Declaration prop)
            {
                if (prop.Values.Count != 1)
                {
                    _nonCompliant = true;
                    return;
                }

                Function f = prop.Values[0] as Function;
                if (f != null && f.Name.Equals("url", StringComparison.OrdinalIgnoreCase))
                {
					string url = f.Arguments;
					if (IsViableImageType(url))
					{
						_hasBackgroundUrl = true;
						Url = f.Arguments;
					}
					else
						_nonCompliant = true;
                }
                else
                {
                    _nonCompliant = true;
                }
            }

            void ProcessRepeat(Declaration prop)
            {
                if (prop.HasValue("repeat"))
                    _nonCompliant = true;
                else
                    Repeat = InterpretRepeatValue(prop.ToString());
            }

			RepeatType InterpretRepeatValue(string value)
			{
				for (int i = 0; i < RepeatTypeValues.Length; ++i)
					if (String.Equals(RepeatTypeValues[i], value, StringComparison.OrdinalIgnoreCase))
						return (RepeatType)i;
				return RepeatType.NotSpritable;
			}

			void ProcessPosition(Declaration prop)
            {
                bool viable = false;
                // only accept two non-zero values
                if (prop.Values.Count == 2)
                {
                    ImagePosition pos;
                    if (TryParsePositionValue(prop.Values[0].ToString(), ImagePosition.left, out pos))
                    {
                        viable = pos != ImagePosition.NonSpritable;
                        LeftPosition = pos;
                    }

                    if (TryParsePositionValue(prop.Values[1].ToString(), ImagePosition.right, out pos))
                    {
                        viable = pos != ImagePosition.NonSpritable;
                        TopPosition = pos;
                    }
                }

                if (!viable)
                    _nonCompliant = true;
            }

            bool TryParsePositionValue(string val, ImagePosition zeroVal, out ImagePosition pos)
            {
                pos = ImagePosition.Unknown;
                try
                {
                    int intVal;
                    if (FindLeadingNumber(val, out intVal))
                    {
                        if (intVal == 0)
                            pos = zeroVal;
                        else
                            pos = ImagePosition.NonSpritable;
                    }
                    else
                    {
                        pos = (ImagePosition)Enum.Parse(typeof(ImagePosition), val, true);
                        pos = zeroVal == pos ? pos : ImagePosition.NonSpritable;
                    }

                    return true;
                }
                catch
                {
                    return false;
                }
            }

			bool IsViableImageType(string url)
			{
				return url.EndsWith(".png", StringComparison.OrdinalIgnoreCase) 
					|| url.EndsWith(".gif", StringComparison.OrdinalIgnoreCase);
			}

            void ProcessShorthand(Declaration prop)
            {
                bool firstPos = true;
                ImagePosition pos;
                List<object> otherValues = new List<object>();

                foreach (object val in prop.Values)
                {
                    Function f = val as Function;

                    // is this a function? Check if it's the URL
                    if (f != null)
                    {
                        if (f.IsUrl)
                        {
							string url = f.Arguments;
							if (!IsViableImageType(url))
							{
								_nonCompliant = true;
								return;
							}

	                        Url = url;
							_hasBackgroundUrl = true;
							continue;
                        }
                        else
                        {
                            _nonCompliant = true;
                            return;
                        }
                    }

                    string strVal = val.ToString();

					if (strVal.Equals("repeat", StringComparison.OrdinalIgnoreCase))
					{
						_nonCompliant = true;
						return;
					}
					else if (strVal.Equals("repeat-x", StringComparison.OrdinalIgnoreCase))
					{
						Repeat = RepeatType.RepeatX;
						continue;
					}
					else if (strVal.Equals("repeat-y", StringComparison.OrdinalIgnoreCase))
					{
						Repeat = RepeatType.RepeatY;
						continue;
					}

                    // is this a position value?
                    if (TryParsePositionValue(strVal, firstPos ? ImagePosition.left : ImagePosition.top, out pos))
                    {
                        if (pos == ImagePosition.NonSpritable)
                        {
                            _nonCompliant = true;
                            return;
                        }

                        if (firstPos)
                            LeftPosition = pos;
                        else
                            TopPosition = pos;
                        firstPos = false;

                        continue;
                    }

                    // We don't know what this is, dump it in the other values
                    otherValues.Add(strVal);
                }

                if (otherValues.Count > 0)
                    Properties.Add(new Declaration("background", otherValues));
            }

            bool FindLeadingNumber(string s, out int val)
            {
                char[] chars = s.TakeWhile(x => Char.IsDigit(x) || x == '-').ToArray();

                if (chars.Length == 0)
                {
                    val = 0;
                    return false;
                }
                else
                {
                    val = Int32.Parse(new String(chars));
                    return true;
                }
            }

            bool IsNonZero(string s)
            {
                int val;
                return FindLeadingNumber(s, out val) ? val != 0 : false;
            }

            public bool HasAnyValue
            {
                get { 
                    return Repeat > RepeatType.NotSpecified 
                        || !String.IsNullOrEmpty(Url)
                        || LeftPosition.HasValue
                        || TopPosition.HasValue;
                }
            }
        }

        public static RuleSet Create(string selector, List<Declaration> properties)
        {
            WorkingSet ws = new WorkingSet(properties);
            if (ws.IsSpriteable)
                return new SpriteableRuleSet(selector, ws.Properties)
                {
                    LeftPosition = ws.LeftPosition,
                    TopPosition = ws.TopPosition,
                    Repeat = ws.Repeat,
                    Url = ws.Url
                };
            else
                return new RuleSet(selector, properties);
        }
    }
}
